package com.reactnativestripesdk

import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.Build
import android.text.InputFilter
import android.view.View
import android.widget.FrameLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.setMargins
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.views.text.ReactTypefaceUtils
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.reactnativestripesdk.utils.PostalCodeUtilities
import com.reactnativestripesdk.utils.getIntOr
import com.reactnativestripesdk.utils.getIntOrNull
import com.reactnativestripesdk.utils.getValOr
import com.reactnativestripesdk.utils.hideSoftKeyboard
import com.reactnativestripesdk.utils.mapCardBrand
import com.reactnativestripesdk.utils.mapToPreferredNetworks
import com.reactnativestripesdk.utils.showSoftKeyboard
import com.stripe.android.core.model.CountryCode
import com.stripe.android.databinding.StripeCardFormViewBinding
import com.stripe.android.databinding.StripeCardMultilineWidgetBinding
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.view.CardFormView
import com.stripe.android.view.CardInputListener

@SuppressLint("ViewConstructor")
class CardFormView(
  private val context: ThemedReactContext,
) : FrameLayout(context) {
  @SuppressLint("PrivateResource")
  private var cardForm: CardFormView =
    CardFormView(context, null, com.stripe.android.R.style.StripeCardFormView_Borderless)
  private var dangerouslyGetFullCardDetails: Boolean = false
  private var currentFocusedField: String? = null
  var cardParams: PaymentMethodCreateParams.Card? = null
  var cardAddress: Address? = null
  private val cardFormViewBinding = StripeCardFormViewBinding.bind(cardForm)
  private val multilineWidgetBinding =
    StripeCardMultilineWidgetBinding.bind(cardFormViewBinding.cardMultilineWidget)

  init {
    cardFormViewBinding.cardMultilineWidgetContainer.isFocusable = true
    cardFormViewBinding.cardMultilineWidgetContainer.isFocusableInTouchMode = true
    (cardFormViewBinding.cardMultilineWidgetContainer.layoutParams as MarginLayoutParams).setMargins(
      0,
    )
    addView(cardForm)
    setListeners()

    viewTreeObserver.addOnGlobalLayoutListener { requestLayout() }
  }

  fun setPostalCodeEnabled(value: Boolean) {
    val visibility = if (value) View.VISIBLE else View.GONE

    cardFormViewBinding.cardMultilineWidget.postalCodeRequired = false
    cardFormViewBinding.postalCodeContainer.visibility = visibility
  }

  fun setDefaultValues(defaults: ReadableMap?) {
    setCountry(defaults?.getString("countryCode"))
  }

  fun setDisabled(isDisabled: Boolean) {
    cardForm.isEnabled = !isDisabled
  }

  fun setPreferredNetworks(preferredNetworks: ArrayList<Int>?) {
    cardForm.setPreferredNetworks(mapToPreferredNetworks(preferredNetworks))
  }

  @SuppressLint("RestrictedApi", "VisibleForTests")
  private fun setCountry(countryString: String?) {
    if (countryString != null) {
      cardFormViewBinding.countryLayout.setSelectedCountryCode(CountryCode(countryString))
      cardFormViewBinding.countryLayout.updateUiForCountryEntered(CountryCode(countryString))
    }
    setPostalCodeFilter()
  }

  fun setPlaceHolders(value: ReadableMap?) {
    val numberPlaceholder = getValOr(value, "number", null)
    val expirationPlaceholder = getValOr(value, "expiration", null)
    val cvcPlaceholder = getValOr(value, "cvc", null)
    val postalCodePlaceholder = getValOr(value, "postalCode", null)

    numberPlaceholder?.let {
      multilineWidgetBinding.tlCardNumber.hint = it
    }
    expirationPlaceholder?.let {
      multilineWidgetBinding.tlExpiry.hint = it
    }
    cvcPlaceholder?.let {
      multilineWidgetBinding.tlCvc.hint = it
    }
    postalCodePlaceholder?.let {
      cardFormViewBinding.postalCodeContainer.hint = it
    }
  }

  fun setAutofocus(value: Boolean) {
    if (value) {
      val cardNumberEditText = multilineWidgetBinding.etCardNumber
      cardNumberEditText.requestFocus()
      cardNumberEditText.showSoftKeyboard()
    }
  }

  fun requestFocusFromJS() {
    val cardNumberEditText = multilineWidgetBinding.etCardNumber
    cardNumberEditText.requestFocus()
    cardNumberEditText.showSoftKeyboard()
  }

  fun requestBlurFromJS() {
    val cardNumberEditText = multilineWidgetBinding.etCardNumber
    cardNumberEditText.hideSoftKeyboard()
    cardNumberEditText.clearFocus()
  }

  private fun onChangeFocus() {
    UIManagerHelper
      .getEventDispatcherForReactTag(context, id)
      ?.dispatchEvent(CardFocusChangeEvent(context.surfaceId, id, currentFocusedField))
  }

  @SuppressLint("RestrictedApi", "VisibleForTests")
  fun setCardStyle(value: ReadableMap?) {
    val backgroundColor = getValOr(value, "backgroundColor", null)
    val textColor = getValOr(value, "textColor", null)
    val borderWidth = value.getIntOrNull("borderWidth")
    val borderColor = getValOr(value, "borderColor", null)
    val borderRadius = value.getIntOr("borderRadius", 0)
    val fontSize = value.getIntOrNull("fontSize")
    val fontFamily = getValOr(value, "fontFamily")
    val placeholderColor = getValOr(value, "placeholderColor", null)
    val textErrorColor = getValOr(value, "textErrorColor", null)
    val cursorColor = getValOr(value, "cursorColor", null)

    val editTextBindings =
      setOf(
        cardFormViewBinding.cardMultilineWidget.cardNumberEditText,
        cardFormViewBinding.cardMultilineWidget.cvcEditText,
        cardFormViewBinding.cardMultilineWidget.expiryDateEditText,
        cardFormViewBinding.postalCode,
      )
    val placeholderTextBindings =
      setOf(
        multilineWidgetBinding.tlExpiry,
        multilineWidgetBinding.tlCardNumber,
        multilineWidgetBinding.tlCvc,
        cardFormViewBinding.postalCodeContainer,
      )

    textColor?.let {
      for (binding in editTextBindings) {
        binding.setTextColor(it.toColorInt())
      }
      cardFormViewBinding.countryLayout.countryAutocomplete.setTextColor(it.toColorInt())
    }
    textErrorColor?.let {
      for (binding in editTextBindings) {
        binding.setErrorColor(it.toColorInt())
        cardFormViewBinding.postalCode.setErrorColor(it.toColorInt())
      }
    }
    placeholderColor?.let {
      for (binding in placeholderTextBindings) {
        binding.defaultHintTextColor = ColorStateList.valueOf(it.toColorInt())
      }
    }
    fontSize?.let {
      for (binding in editTextBindings) {
        binding.textSize = it.toFloat()
      }
    }
    fontFamily?.let {
      // Load custom font from assets, and fallback to default system font
      val typeface =
        ReactTypefaceUtils.applyStyles(null, -1, -1, it.takeIf { it.isNotEmpty() }, context.assets)
      for (binding in editTextBindings) {
        binding.typeface = typeface
      }
      for (binding in placeholderTextBindings) {
        binding.typeface = typeface
      }
      cardFormViewBinding.countryLayout.typeface = typeface
      cardFormViewBinding.countryLayout.countryAutocomplete.typeface = typeface
      cardFormViewBinding.errors.typeface = typeface
    }
    cursorColor?.let {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val color = it.toColorInt()
        for (binding in editTextBindings) {
          binding.textCursorDrawable?.setTint(color)
          binding.textSelectHandle?.setTint(color)
          binding.textSelectHandleLeft?.setTint(color)
          binding.textSelectHandleRight?.setTint(color)
          binding.highlightColor = color
        }
      }
    }

    cardFormViewBinding.cardMultilineWidgetContainer.background =
      MaterialShapeDrawable(
        ShapeAppearanceModel()
          .toBuilder()
          .setAllCorners(CornerFamily.ROUNDED, PixelUtil.toPixelFromDIP(borderRadius.toDouble()))
          .build(),
      ).also { shape ->
        shape.strokeWidth = 0.0f
        shape.strokeColor = ColorStateList.valueOf("#000000".toColorInt())
        shape.fillColor = ColorStateList.valueOf("#FFFFFF".toColorInt())
        borderWidth?.let {
          shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble())
        }
        borderColor?.let {
          shape.strokeColor = ColorStateList.valueOf(it.toColorInt())
        }
        backgroundColor?.let {
          shape.fillColor = ColorStateList.valueOf(it.toColorInt())
        }
      }
  }

  fun setDangerouslyGetFullCardDetails(isEnabled: Boolean) {
    dangerouslyGetFullCardDetails = isEnabled
  }

  private fun setListeners() {
    cardForm.setCardValidCallback { isValid, _ ->
      if (isValid) {
        cardForm.paymentMethodCreateParams?.let {
          val cardParamsMap = it.toParamMap()["card"] as HashMap<*, *>

          @SuppressLint("RestrictedApi")
          val cardDetails: MutableMap<String, Any> =
            mutableMapOf(
              "expiryMonth" to cardParamsMap["exp_month"] as Int,
              "expiryYear" to cardParamsMap["exp_year"] as Int,
              "last4" to (it.cardLast4() ?: ""),
              "brand" to mapCardBrand(cardForm.brand),
              "postalCode" to (it.billingDetails?.address?.postalCode ?: ""),
              "country" to (it.billingDetails?.address?.country ?: ""),
            )

          if (dangerouslyGetFullCardDetails) {
            cardDetails["number"] = cardParamsMap["number"] as String
            cardDetails["cvc"] = cardParamsMap["cvc"] as String
          }

          UIManagerHelper
            .getEventDispatcherForReactTag(context, id)
            ?.dispatchEvent(
              CardFormCompleteEvent(
                context.surfaceId,
                id,
                cardDetails,
                isValid,
                dangerouslyGetFullCardDetails,
              ),
            )

          cardAddress =
            Address
              .Builder()
              .setPostalCode(it.billingDetails?.address?.postalCode)
              .setCountry(it.billingDetails?.address?.country)
              .build()

          cardFormViewBinding.cardMultilineWidget.paymentMethodCard?.let { params ->
            cardParams = params
          }
        }
      } else {
        cardParams = null
        cardAddress = null
        UIManagerHelper
          .getEventDispatcherForReactTag(context, id)
          ?.dispatchEvent(
            CardFormCompleteEvent(
              context.surfaceId,
              id,
              null,
              isValid,
              dangerouslyGetFullCardDetails,
            ),
          )
      }
    }

    val cardNumberEditText = multilineWidgetBinding.etCardNumber
    val cvcEditText = multilineWidgetBinding.etCvc
    val expiryEditText = multilineWidgetBinding.etExpiry
    val postalCodeEditText = cardFormViewBinding.postalCode

    cardNumberEditText.onFocusChangeListener =
      OnFocusChangeListener { _, hasFocus ->
        currentFocusedField =
          if (hasFocus) CardInputListener.FocusField.CardNumber.toString() else null
        onChangeFocus()
      }
    cvcEditText.onFocusChangeListener =
      OnFocusChangeListener { _, hasFocus ->
        currentFocusedField = if (hasFocus) CardInputListener.FocusField.Cvc.toString() else null
        onChangeFocus()
      }
    expiryEditText.onFocusChangeListener =
      OnFocusChangeListener { _, hasFocus ->
        currentFocusedField =
          if (hasFocus) CardInputListener.FocusField.ExpiryDate.toString() else null
        onChangeFocus()
      }
    postalCodeEditText.onFocusChangeListener =
      OnFocusChangeListener { _, hasFocus ->
        currentFocusedField =
          if (hasFocus) CardInputListener.FocusField.PostalCode.toString() else null
        onChangeFocus()
      }
  }

  private fun setPostalCodeFilter() {
    cardFormViewBinding.postalCode.filters =
      arrayOf(
        *cardFormViewBinding.postalCode.filters,
        createPostalCodeInputFilter(),
      )
  }

  @SuppressLint("RestrictedApi", "VisibleForTests")
  private fun createPostalCodeInputFilter(): InputFilter {
    return InputFilter { charSequence, start, end, _, _, _ ->
      if (cardFormViewBinding.countryLayout.getSelectedCountryCode() == CountryCode.US) {
        // Rely on CardFormView's built-in US postal code filter
        return@InputFilter null
      }

      for (i in start until end) {
        if (!PostalCodeUtilities.isValidGlobalPostalCodeCharacter(charSequence[i])) {
          return@InputFilter ""
        }
      }
      return@InputFilter null
    }
  }

  override fun requestLayout() {
    super.requestLayout()
    post(mLayoutRunnable)
  }

  private val mLayoutRunnable =
    Runnable {
      measure(
        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
      )
      layout(left, top, right, bottom)
    }
}
